﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Program.cs" company="">
//   
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace ExecutionTool
{
    using System;
    using System.Collections.Generic;
    using System.Reflection;

    using ExecutionTool.Common;
    using ExecutionTool.Profile;

    using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.Unity;
    using Microsoft.Practices.EnterpriseLibrary.Logging;
    using Microsoft.Practices.ServiceLocation;
    using Microsoft.Practices.Unity;

    internal class Program
    {
        #region Properties

        private static LogWriter Logger
        {
            get
            {
                return ServiceLocator.Current.GetInstance<LogWriter>();
            }
        }

        #endregion

        #region Methods

        private static string FetchArgValue(IList<string> args, int argIndex, string argName)
        {
            string error = string.Format("Argument value for [-{0}] not found.", argName);

            if (++argIndex >= args.Count)
            {
                throw new ArgumentException(error);
            }

            var value = args[argIndex];

            if (value[0] == '/' || value[0] == '-')
            {
                throw new ArgumentException(error);
            }

            return value;
        }

        private static void LogUnexpectedError(Exception ex)
        {
            var sfe = ex as StepFaultException;

            if (sfe != null)
            {
                Logger.LogError(string.Format("Failed on step {0} due to --> {1}", sfe.StepType, sfe), sfe.ErrorCode);
            }
            else
            {
                Logger.LogError("Unexpected error happened. --> " + ex, ErrorCodes.UnexpectedError);
            }

            Environment.Exit(-1);
        }

        private static void Main(string[] args)
        {
            var p = new Program();
            p.Execute(args);
        }

        private static void PrintLogo()
        {
            var assembly = Assembly.GetExecutingAssembly();

            Console.WriteLine("SQL Azure Data Protector, Execution Tool ({0})", assembly.GetName().Version);

            ////Console.WriteLine("Copyright (C) Microsoft Corporation. All rights reserved.");
            Console.WriteLine();
        }

        private static void PrintUsage()
        {
            /*
             * -h(elp) | -?
             * -p(rofile) <p1>
             * -b(ackup)
             * -r(estore) <bacpac>
             * -r(e)n(ame) <new_db_name>
             * -s(uppress)no(tification)
             * -t(est)c(onnectivity)o(nly)
             */
            var assembly = Assembly.GetExecutingAssembly();
            var appName = assembly.GetName().Name;

            Console.WriteLine("Command Line Parameters:");
            Console.WriteLine("-P[rofile] <profilename>     The profile containing the endpoint information.");
            Console.WriteLine("-H[elp] | -?                 Show this help text.");
            Console.WriteLine("-B[ackup]                    Perform a backup operation.");
            Console.WriteLine("-R[estore] <bacpacname>      Perform a restore operation from the specified backpac file.");
            Console.WriteLine("-R[e]n[ame] <newdbname>      Use this new database name when restoring from a backup.");
            Console.WriteLine("-S(uppress)No(tification)    Suppress the notification after an operation.");
            Console.WriteLine("-T(est)C(onnectivity)O(nly)  Perform connectivity tests only.");

            Console.WriteLine();
            Console.WriteLine();

            Console.WriteLine("Usage:");
            Console.WriteLine("Backup a database by using a specified profile:");
            Console.WriteLine("   {0} -p profile1 -b", appName);
            Console.WriteLine();
            Console.WriteLine("Restore a database by using a specified profile and a specified bacpac file: (*1)");
            Console.WriteLine("   {0} -p profile1 -r pocdb.2012-05-01-00-30-00.bacpac", appName);
            Console.WriteLine();
            Console.WriteLine("Restore a database by using a specified profile, a specified bacpac file and also a new database name: (*1)");
            Console.WriteLine("   {0} -p profile1 -r pocdb.2012-05-01-00-30-00.bacpac -rn NewDatabaseName", appName);
            Console.WriteLine();
            Console.WriteLine("Backup a database by using a specified profile and suppress the completion notification:");
            Console.WriteLine("   {0} -p profile1 -b -sno", appName);
            Console.WriteLine();
            Console.WriteLine("Only test connectivity of the specified profile: (*2)");
            Console.WriteLine("   {0} -p profile1 -tco", appName);
            Console.WriteLine();

            Console.WriteLine("*Notes and caveats:");
            Console.WriteLine("1. On restore the database with the same name must not exist. A new database is");
            Console.WriteLine("   always created. The original database name, edition and max size will be automatically applied.");
            Console.WriteLine("2. By default, the connectivity will always be tested before running any operations.");

            Console.WriteLine();
        }

        private static CommandLineOptions ProcessCommandLine(IList<string> args)
        {
            var commandLineOptions = new CommandLineOptions();

            for (var i = 0; i < args.Count; i++)
            {
                if (args[i][0] == '/' || args[i][0] == '-')
                {
                    var param = args[i].Substring(1);
                    switch (param.ToUpper())
                    {
                        case "PROFILE":
                        case "P":
                            commandLineOptions.ProfileName = FetchArgValue(args, i, param.ToUpper());
                            i++;
                            break;

                        case "BACKUP":
                        case "B":
                            commandLineOptions.OperationType = OperationType.Backup;
                            break;

                        case "RESTORE":
                        case "R":
                            commandLineOptions.OperationType = OperationType.Restore;
                            commandLineOptions.RestoreBacpacName = FetchArgValue(args, i, param.ToUpper());
                            i++;
                            break;

                        case "RENAME":
                        case "RN":
                            commandLineOptions.RestoreDatabaseRename = FetchArgValue(args, i, param.ToUpper());
                            i++;
                            break;

                        case "SUPPRESSNOTIFICATION":
                        case "SNO":
                            commandLineOptions.ToSuppressNotification = true;
                            break;

                        case "TESTCONNECTIVITYONLY":
                        case "TCO":
                            commandLineOptions.OperationType = OperationType.TestConnectionOnly;
                            break;

                        case "HELP":
                        case "H":
                        case "?":
                            commandLineOptions.OperationType = OperationType.Help;
                            break;

                        default:
                            Console.WriteLine("[ERROR] UNKNOWN PARAMETER: {0}", param);
                            commandLineOptions.OperationType = OperationType.Help;
                            break;
                    }
                }
                else
                {
                    commandLineOptions.OperationType = OperationType.Help;
                    break;
                }
            }

            ValidateOptions(commandLineOptions);

            return commandLineOptions;
        }

        private static void SetupServiceLocator()
        {
            IUnityContainer container = new UnityContainer();
            IServiceLocator locator = new UnityServiceLocator(container);

            // Make sure this method call before any other type registrations.
            ServiceLocator.SetLocatorProvider(() => locator);

            container.AddNewExtension<EnterpriseLibraryCoreExtension>();
        }

        private static void ValidateOptions(CommandLineOptions options)
        {
            if (options == null)
            {
                throw new ArgumentNullException("options");
            }

            if ((options.OperationType == OperationType.Backup || options.OperationType == OperationType.Restore)
                && string.IsNullOrWhiteSpace(options.ProfileName))
            {
                throw new ArgumentException("Profile name must be specified.");
            }

            if (options.OperationType == OperationType.Restore && string.IsNullOrWhiteSpace(options.RestoreBacpacName))
            {
                throw new ArgumentException("The backpac name from which to restore must be specified.");
            }
        }

        private void Execute(IList<string> args)
        {
            /*
             * parse arg
             * 
             * Run operation
             */
            AppDomain.CurrentDomain.UnhandledException += (sender, eventArgs) => LogUnexpectedError((Exception)eventArgs.ExceptionObject);

            PrintLogo();

            SetupServiceLocator();

            try
            {
                var options = ProcessCommandLine(args);

                switch (options.OperationType)
                {
                    case OperationType.Backup:
                        var backupRunner = new BackupRunner(options);
                        backupRunner.Run();
                        break;

                    case OperationType.Restore:
                        var restoreRunner = new RestoreRunner(options);
                        restoreRunner.Run();
                        break;

                    case OperationType.TestConnectionOnly:
                        var p = ProfileFacade.LoadProfile(options.ProfileName);
                        try
                        {
                            var tcoRunner = new BackupRunner(options);
                            tcoRunner.TestConnectivity(p);
                            Logger.LogInformation("Test connectivity succeeded.");
                        }
                        catch (Exception ex)
                        {
                            throw new StepFaultException(
                                "Connectivity test failed, please check log files for details, fix the problem and re-run the operation.", 
                                StepType.TestConnection, 
                                ErrorCodes.UnexpectedTestConnectionError, 
                                ex);
                        }

                        break;

                    default:
                        PrintUsage();
                        break;
                }
            }
            catch (Exception ex)
            {
                LogUnexpectedError(ex);
            }
        }

        #endregion
    }
}
